﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using AnaxibiaStudios.Smug.Advanced;
using System.Reflection;
using AnaxibiaStudios.Smug.Internal;
using AnaxibiaStudios.Smug;

namespace SmugTest
{
  [TestClass]
  public class MemberIterationTest
  {
    private MemberCollection GetHelper<TargetType>()
    {
      var builder = new SourceBuilder(typeof(TargetType));
      return builder.MemberCollection;
    }

    public class Empty
    {
    }

    [TestMethod]
    public void NoMethodsDefaultTest()
    {
      Assert.AreEqual(0, GetHelper<Empty>().Count(m => m.MemberType != MemberTypes.Constructor));
      Create.Proxy<I_InterfaceButEmpty>();
    }

    public class SealedOnly
    {
      public void Foo() { }
      protected void Bar() { }
      private void Baz() { }

      public int FooProp { get; set; }
      protected int BarProp { get { throw new NotImplementedException(); } }
      private int BazProp { set { throw new NotImplementedException(); } }
    }

    [TestMethod]
    public void SealedOnlyTest()
    {
      Assert.AreEqual(0, GetHelper<SealedOnly>().Count(ex => !ex.IsSealed));
    }

    public interface I_InterfaceButEmpty
    {
      void Foo();
    }

    public class InterfaceButEmpty : I_InterfaceButEmpty
    {
      public void Foo() { }
    }

    [TestMethod]
    public void InterfaceButEmptyTest()
    {
      Assert.AreEqual(0, GetHelper<InterfaceButEmpty>().Count(ex => !ex.IsSealed));
    }

    public abstract class AbstractButEmpty
    {
      public void Foo() { }
      protected void Bar() { }
      private void Baz() { }

      public int FooProp { get; set; }
      protected int BarProp { get { throw new NotImplementedException(); } }
      private int BazProp { set { throw new NotImplementedException(); } }
    }

    [TestMethod]
    public void AbstractButEmptyTest()
    {
      var members = GetHelper<AbstractButEmpty>();
      Assert.AreEqual(0, members.Count(ex => !ex.IsSealed));
    }

    public class TestCase1
    {
      public virtual decimal Foo(int i, out string s, ref double d) { throw new NotImplementedException(); }
      protected virtual void Bar() { }
    }

    [TestMethod]
    public void TestCase1Test()
    {
      var members = GetHelper<TestCase1>();
      Assert.AreEqual(2, members.Count(m => m.MemberType != MemberTypes.Constructor));

      var foo = members["Foo"];
      Assert.IsTrue(foo.IsPublic);
      Assert.IsTrue(foo.IsVirtual);

      Assert.AreEqual("Foo", foo.Name);
      Assert.AreEqual("SmugTest.MemberIterationTest.TestCase1.", foo.ExplicitPrefix);

      Assert.AreEqual(typeof(decimal), foo.ReturnType);
      Assert.AreEqual(3, foo.Parameters.Length);

      Assert.AreEqual(typeof(int), foo.Parameters[0].Type);
      Assert.AreEqual(ParameterPassBy.Standard, foo.Parameters[0].PassBy);
      Assert.AreEqual("i", foo.Parameters[0].Name);

      Assert.AreEqual(typeof(string).MakeByRefType(), foo.Parameters[1].Type);
      Assert.AreEqual(ParameterPassBy.Out, foo.Parameters[1].PassBy);
      Assert.AreEqual("s", foo.Parameters[1].Name);

      Assert.AreEqual(typeof(double).MakeByRefType(), foo.Parameters[2].Type);
      Assert.AreEqual(ParameterPassBy.Ref, foo.Parameters[2].PassBy);
      Assert.AreEqual("d", foo.Parameters[2].Name);

      var bar = members["Bar"];
      Assert.IsFalse(bar.IsPublic);
      Assert.IsTrue(bar.IsVirtual);

      Assert.AreEqual("Bar", bar.Name);
      Assert.AreEqual("SmugTest.MemberIterationTest.TestCase1.", bar.ExplicitPrefix);

      Assert.AreEqual(typeof(void), bar.ReturnType);
      Assert.AreEqual(0, bar.Parameters.Length);
    }

    public interface I_TestCase2
    {
      int Foo(out int i);
      double Bar(string s);
    }

    public abstract class TestCase2 : I_TestCase2
    {
      int I_TestCase2.Foo(out int i) { throw new NotImplementedException(); }
      protected abstract string Foo(ref int i);

      public virtual double Bar(string s) { throw new NotImplementedException(); }
    }

    [TestMethod]
    public void TestCase2Test()
    {
      var members = GetHelper<TestCase2>();
      Assert.AreEqual(3, members.Count(m => m.MemberType != MemberTypes.Constructor));
      var xfoo = members["Foo", true];
      var sfoo = members["Foo", false];
      Assert.AreNotEqual(xfoo, sfoo);

      Assert.IsFalse(xfoo.IsPublic);
      Assert.IsTrue(xfoo.IsExplicit);
      Assert.AreEqual("SmugTest.MemberIterationTest.TestCase2.", xfoo.ExplicitPrefix);
      Assert.AreEqual("Foo", xfoo.Name);
      Assert.AreEqual(typeof(int), xfoo.ReturnType);
      Assert.AreEqual(1, xfoo.Parameters.Length);
      Assert.AreEqual(typeof(int).MakeByRefType(), xfoo.Parameters[0].Type);
      Assert.AreEqual(ParameterPassBy.Out, xfoo.Parameters[0].PassBy);
      Assert.AreEqual("i", xfoo.Parameters[0].Name);

      Assert.IsFalse(sfoo.IsPublic);
      Assert.IsTrue(sfoo.IsAbstract);
      Assert.AreEqual("SmugTest.MemberIterationTest.TestCase2.", sfoo.ExplicitPrefix);
      Assert.AreEqual("Foo", sfoo.Name);
      Assert.AreEqual(typeof(string), sfoo.ReturnType);
      Assert.AreEqual(1, sfoo.Parameters.Length);
      Assert.AreEqual(typeof(int).MakeByRefType(), sfoo.Parameters[0].Type);
      Assert.AreEqual(ParameterPassBy.Ref, sfoo.Parameters[0].PassBy);
      Assert.AreEqual("i", sfoo.Parameters[0].Name);

      var bar = members["Bar"];
      Assert.IsTrue(bar.IsPublic);
      Assert.IsTrue(bar.IsVirtual);
      Assert.AreEqual("SmugTest.MemberIterationTest.TestCase2.", bar.ExplicitPrefix);
      Assert.AreEqual("Bar", bar.Name);
      Assert.AreEqual(typeof(double), bar.ReturnType);
      Assert.AreEqual(1, bar.Parameters.Length);
      Assert.AreEqual(typeof(string), bar.Parameters[0].Type);
      Assert.AreEqual(ParameterPassBy.Standard, bar.Parameters[0].PassBy);
      Assert.AreEqual("s", bar.Parameters[0].Name);
    }

    public interface I_TestCase3_A
    {
      int Foo();
    }

    public interface I_TestCase3_B : I_TestCase3_A
    {
      int Foo();
    }

    [TestMethod]
    public void TestCase3Test()
    {
      var members = GetHelper<I_TestCase3_B>();
      Assert.AreEqual(3, members.Count);

      Assert.AreEqual(typeof(I_TestCase3_B), members["Foo", false].DeclaringType, "declaring type for default");
      Assert.AreEqual(typeof(I_TestCase3_A), members["Foo", true].DeclaringType, "declaring type for explicit");
    }

    public interface I_TestCase4_A
    {
      int Shared(string s);
      int Shared(double s);
      void A();
    }
    public interface I_TestCase4_B
    {
      int Shared(string s);
      int Shared(Action s);
      void B();
    }
    public interface I_TestCase4 : I_TestCase4_A, I_TestCase4_B
    {
      decimal Shared(ref int i);
      void Shared(string s);
      void C();
    }

    [TestMethod]
    public void TestCase4Test()
    {
      var members = GetHelper<I_TestCase4>();

      Assert.AreEqual(10, members.Count);
      Assert.AreEqual(typeof(I_TestCase4_A), members["A"].DeclaringType, "declaring type for A");
      Assert.AreEqual(typeof(I_TestCase4_B), members["B"].DeclaringType, "declaring type for B");
      Assert.AreEqual(typeof(I_TestCase4), members["C"].DeclaringType, "declaring type for C");

      var sharedGroup = members.Where(msb => msb.Name == "Shared").Select(ex => ex).ToArray();
      ContainsHelper(ref sharedGroup, true, typeof(I_TestCase4_A), typeof(int), typeof(string));
      ContainsHelper(ref sharedGroup, false, typeof(I_TestCase4_A), typeof(int), typeof(double));

      ContainsHelper(ref sharedGroup, true, typeof(I_TestCase4_B), typeof(int), typeof(string));
      ContainsHelper(ref sharedGroup, false, typeof(I_TestCase4_B), typeof(int), typeof(Action));

      ContainsHelper(ref sharedGroup, false, typeof(I_TestCase4), typeof(decimal), typeof(int).MakeByRefType());
      ContainsHelper(ref sharedGroup, false, typeof(I_TestCase4), typeof(void), typeof(string));

      Assert.AreEqual(0, sharedGroup.Count(), "remaining shared");
    }

    private void ContainsHelper(ref MemberInfoEx[] member, bool expl, Type decl, Type ret, params Type[] parms)
    {
      var matches = member
        .Where(m => ((expl && m.IsExplicit) || m.IsVirtual))
        .Where(m => m.MemberInfo.DeclaringType == decl)
        .Where(m => m.ReturnType == ret)
        .Where(m => Matches(m.Parameters.Select(pex => pex.Type), parms));

      Assert.AreEqual(1, matches.Count(), string.Format("Matches for {0}:{1}:{2}:{3}", expl, decl, ret, string.Join<Type>(", ", parms)));

      var match = matches.First();
      member = member.Where(m => m != match).ToArray();
    }

    private bool Matches<T>(IEnumerable<T> a, IEnumerable<T> b) where T : class
    {
      if (a == null || b == null)
        return false;

      var a_ = a.ToArray();
      var b_ = b.ToArray();

      if (a_.Length != b_.Length)
        return false;

      for (int i = 0; i < a_.Length; i++)
      {
        if (a_[i] != b_[i])
          return false;
      }

      return true;
    }

    class PropertyAccessModifiers
    {
      public int Foo { get; set; }
      public int Foo1 { get; protected set; }
      public int Foo2 { get; private set; }
      public int Foo3 { protected get; set; }
      //public int Foo4 { protected get; protected set; }
      //public int Foo5 { protected get; private set; }
      public int Foo6 { private get; set; }
      //public int Foo7 { private get; protected set; }
      //public int Foo8 { private get; private set; }

      protected int Bar { get; set; }
      //protected int Bar1 { get; protected set; }
      protected int Bar2 { get; private set; }
      //protected int Bar3 { protected get; set; }
      //protected int Bar4 { protected get; protected set; }
      //protected int Bar5 { protected get; private set; }
      protected int Bar6 { private get; set; }
      //protected int Bar7 { private get; protected set; }
      //protected int Bar8 { private get; private set; }

      private int Baz { get; set; }
      //private int Baz1 { get; protected set; }
      //private int Baz2 { get; private set; }
      //private int Baz3 { protected get; set; }
      //private int Baz4 { protected get; protected set; }
      //private int Baz5 { protected get; private set; }
      //private int Baz6 { private get; set; }
      //private int Baz7 { private get; protected set; }
      //private int Baz8 { private get; private set; }

      public int FooRead { get { throw new NotImplementedException(); } }
      protected int BarRead { get { throw new NotImplementedException(); } }
      private int BazRead { get { throw new NotImplementedException(); } }

      public int FooWrite { set { throw new NotImplementedException(); } }
      protected int BarWrite { set { throw new NotImplementedException(); } }
      private int BazWrite { set { throw new NotImplementedException(); } }
    }

    [TestMethod]
    public void PropertyAccessModifiersTest()
    {
      PropertyAccessModifierHelper<PropertyAccessModifiers>("Foo", Access.Public, Access.Public, Access.Public);
      PropertyAccessModifierHelper<PropertyAccessModifiers>("Foo1", Access.Public, Access.Public, Access.Protected);
      PropertyAccessModifierHelper<PropertyAccessModifiers>("Foo2", Access.Public, Access.Public, Access.Private);
      PropertyAccessModifierHelper<PropertyAccessModifiers>("Foo3", Access.Public, Access.Protected, Access.Public);
      PropertyAccessModifierHelper<PropertyAccessModifiers>("Foo6", Access.Public, Access.Private, Access.Public);

      PropertyAccessModifierHelper<PropertyAccessModifiers>("Bar", Access.Protected, Access.Protected, Access.Protected);
      PropertyAccessModifierHelper<PropertyAccessModifiers>("Bar2", Access.Protected, Access.Protected, Access.Private);
      PropertyAccessModifierHelper<PropertyAccessModifiers>("Bar6", Access.Protected, Access.Private, Access.Protected);

      PropertyAccessModifierHelper<PropertyAccessModifiers>("Baz", Access.Private, Access.Private, Access.Private);

      PropertyAccessModifierHelper<PropertyAccessModifiers>("FooRead", Access.Public, Access.Public, Access.Null);
      PropertyAccessModifierHelper<PropertyAccessModifiers>("BarRead", Access.Protected, Access.Protected, Access.Null);
      PropertyAccessModifierHelper<PropertyAccessModifiers>("BazRead", Access.Private, Access.Private, Access.Null);

      PropertyAccessModifierHelper<PropertyAccessModifiers>("FooWrite", Access.Public, Access.Null, Access.Public);
      PropertyAccessModifierHelper<PropertyAccessModifiers>("BarWrite", Access.Protected, Access.Null, Access.Protected);
      PropertyAccessModifierHelper<PropertyAccessModifiers>("BazWrite", Access.Private, Access.Null, Access.Private);
    }

    private void PropertyAccessModifierHelper<TargetType>(string propertyName, Access declared, Access getter, Access setter)
    {
      var type = typeof(TargetType);
      var prop = type.GetProperty(propertyName, Constants.Flags);
      var meta = MemberInfoEx.SoloWrap(new SourceBuilder(type), prop);

      Assert.AreEqual(declared, GetAccessValue(meta), string.Format("property {0} declared access", propertyName));
      Assert.AreEqual(getter, GetAccessValue(meta.Getter), string.Format("property {0} read access", propertyName));
      Assert.AreEqual(setter, GetAccessValue(meta.Setter), string.Format("property {0} set access", propertyName));
    }

    private Access GetAccessValue(MemberInfoEx target)
    {
      var prop = typeof(MemberInfoEx).GetProperty(typeof(Access).Name, Constants.Flags);
      return (Access)prop.GetValue(target, new object[] { });
    }

    interface I_EventAccessModifiers
    {
      event Action IFoo;
    }

    class EventAccessModifiers : I_EventAccessModifiers
    {
      public event Action Foo;
      protected event Action Bar;
      private event Action Baz;

      event Action I_EventAccessModifiers.IFoo { add { } remove { } }
      protected virtual event Action<int> IFoo;
    }

    [TestMethod]
    public void EventAccessModifiersTest()
    {
      EventAccessModifierHelper<EventAccessModifiers>("Foo", Access.Public, Access.Public, Access.Public);
      EventAccessModifierHelper<EventAccessModifiers>("Bar", Access.Protected, Access.Protected, Access.Protected);
      EventAccessModifierHelper<EventAccessModifiers>("Baz", Access.Private, Access.Private, Access.Private);
      EventAccessModifierHelper<EventAccessModifiers>("SmugTest.MemberIterationTest.I_EventAccessModifiers.IFoo", Access.Private, Access.Private, Access.Private);
      EventAccessModifierHelper<EventAccessModifiers>("IFoo", Access.Protected, Access.Protected, Access.Protected);

      var members = GetHelper<EventAccessModifiers>();
      var ifoo = members["IFoo", false];
      Assert.IsTrue(ifoo.IsVirtual, "IFoo state");
    }

    private void EventAccessModifierHelper<TargetType>(string eventName, Access declared, Access add, Access remove)
    {
      var type = typeof(TargetType);
      var ev = type.GetEvent(eventName, Constants.Flags);
      var ex = MemberInfoEx.SoloWrap(new SourceBuilder(type), ev);

      Assert.AreEqual(declared, GetAccessValue(ex), string.Format("event {0} declared access", eventName));
      Assert.AreEqual(add, GetAccessValue(ex.Add), string.Format("event {0} add access", eventName));
      Assert.AreEqual(remove, GetAccessValue(ex.Remove), string.Format("event {0} remove access", eventName));
    }

    class EventTestCase1<T>
    {
      public event System.Action<T> SimpleEvent;
      public virtual int Foo { set { throw new NotImplementedException(); } }
      public EventTestCase1() { }
      public EventTestCase1(int i) { }
    }

    [TestMethod]
    public void EventTestCase1Test()
    {
      var members = GetHelper<EventTestCase1<int>>();
      Assert.AreEqual(2, members.Count(m => m.MemberType != MemberTypes.Constructor));

      var ev = members["SimpleEvent"];
      Assert.AreEqual("SimpleEvent", ev.Name);
      Assert.AreEqual(Access.Public, GetAccessValue(ev));
      Assert.AreEqual(typeof(void), ev.ReturnType);
      Assert.AreEqual(1, ev.Parameters.Length);
      Assert.AreEqual(typeof(int), ev.Parameters[0].Type);

      var prop = members["Foo"];
      Assert.AreEqual("Foo", prop.Name);
      Assert.AreEqual(typeof(int), prop.ReturnType);
      Assert.AreEqual(0, prop.Parameters.Length);
      Assert.AreEqual(Access.Public, GetAccessValue(prop));
      Assert.IsTrue(prop.IsVirtual);
      Assert.IsTrue((prop as PropertyInfoEx).Setter.IsPublic);
      Assert.IsTrue((prop as PropertyInfoEx).Getter.IsNull);
    }

    interface I_HasBodyTestCase1
    {
      void I1();
      void I2();
      void I3();
      void I4();
    }

    abstract class HasBodyTestCase1 : I_HasBodyTestCase1
    {
      public void I1() { }

      void I_HasBodyTestCase1.I2() { }
      public void I2() { }

      void I_HasBodyTestCase1.I3() { }
      public virtual void I3() { }

      void I_HasBodyTestCase1.I4() { }
      public abstract void I4();

      public void _1() { }
      protected void _2() { }
      private void _3() { }

      public virtual void _4() { }
      protected virtual void _5() { }

      public abstract void _6();
      protected abstract void _7();
    }

    [TestMethod]
    public void HasBodyTest1()
    {
      var members = GetHelper<I_HasBodyTestCase1>();
      Assert.AreEqual(BaseCall.Null, members["I1"].BaseCall, "interface I1");
      Assert.AreEqual(BaseCall.Null, members["I2"].BaseCall, "interface I2");
      Assert.AreEqual(BaseCall.Null, members["I3"].BaseCall, "interface I3");
      Assert.AreEqual(BaseCall.Null, members["I4"].BaseCall, "interface I4");

      members = GetHelper<HasBodyTestCase1>();
      Assert.AreEqual(BaseCall.Default, members["I1", false].BaseCall, "class I1, not explicit");

      Assert.AreEqual(BaseCall.Default, members["I2", false].BaseCall, "class I2, not explicit");
      Assert.AreEqual(BaseCall.Explicit, members["I2", true].BaseCall, "class I2, explicit");

      Assert.AreEqual(BaseCall.Default, members["I3", false].BaseCall, "class I3, not explicit");
      Assert.AreEqual(BaseCall.Explicit, members["I3", true].BaseCall, "class I3, explicit");

      Assert.AreEqual(BaseCall.Null, members["I4", false].BaseCall, "class I4, not explicit");
      Assert.AreEqual(BaseCall.Explicit, members["I4", true].BaseCall, "class I4, explicit");

      Assert.AreEqual(BaseCall.Default, members["_1"].BaseCall, "_1");
      Assert.AreEqual(BaseCall.Default, members["_2"].BaseCall, "_2");
      Assert.AreEqual(BaseCall.Default, members["_3"].BaseCall, "_3");
      Assert.AreEqual(BaseCall.Default, members["_4"].BaseCall, "_4");
      Assert.AreEqual(BaseCall.Default, members["_5"].BaseCall, "_5");
      Assert.AreEqual(BaseCall.Null, members["_6"].BaseCall, "_6");
      Assert.AreEqual(BaseCall.Null, members["_7"].BaseCall, "_7");
    }

    interface I_HasBodyTestCase2
    {
      event Action I1;
      event Action I2;
      event Action I3;
      event Action I4;
    }

    abstract class HasBodyTestCase2 : I_HasBodyTestCase2
    {
      public event Action I1;

      event Action I_HasBodyTestCase2.I2 { add { } remove { } }
      public event Action I2;

      event Action I_HasBodyTestCase2.I3 { add { } remove { } }
      public virtual event Action I3;

      event Action I_HasBodyTestCase2.I4 { add { } remove { } }
      public abstract event Action I4;
    }

    [TestMethod]
    public void HasBodyTest2()
    {
      var members = GetHelper<I_HasBodyTestCase2>();
      Assert.AreEqual(5, members.Count);

      Assert.AreEqual(BaseCall.Null, members["I1"].BaseCall);
      Assert.AreEqual(BaseCall.Null, members["I2"].BaseCall);
      Assert.AreEqual(BaseCall.Null, members["I3"].BaseCall);
      Assert.AreEqual(BaseCall.Null, members["I4"].BaseCall);

      members = GetHelper<HasBodyTestCase2>();
      Assert.AreEqual(BaseCall.Default, members["I1", false].BaseCall, "class, I1, not explicit");
      Assert.AreEqual(BaseCall.Explicit, members["I2", true].BaseCall, "class, I2, explicit");
      Assert.AreEqual(BaseCall.Default, members["I2", false].BaseCall, "class, I2, not explicit");
      Assert.AreEqual(BaseCall.Explicit, members["I3", true].BaseCall, "class, I3, explicit");
      Assert.AreEqual(BaseCall.Default, members["I3", false].BaseCall, "class, I3, not explicit");
      Assert.AreEqual(BaseCall.Explicit, members["I4", true].BaseCall, "class, I4, explicit");
      Assert.AreEqual(BaseCall.Null, members["I4", false].BaseCall, "class, I4, not explicit");
    }

    interface I_HasBodyTestCase3
    {
      int I1 { get; set; }
      int I2 { get; set; }
      int I3 { get; set; }
      int I4 { get; set; }
    }

    abstract class HasBodyTestCase3 : I_HasBodyTestCase3
    {
      public int I1 { get; set; }

      int I_HasBodyTestCase3.I2 { get{throw new NotImplementedException(); } set{throw new NotImplementedException(); } }
      public event Action I2;

      int I_HasBodyTestCase3.I3 { get{throw new NotImplementedException(); } set{throw new NotImplementedException(); } }
      public virtual int I3 { get; set; }

      int I_HasBodyTestCase3.I4 { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
      public abstract int I4 { get; set; }
    }

    [TestMethod]
    public void HasBodyTest3()
    {
      var members = GetHelper<I_HasBodyTestCase3>();
      Assert.AreEqual(5, members.Count);

      Assert.AreEqual(BaseCall.Null, members["I1"].BaseCall);
      Assert.AreEqual(BaseCall.Null, members["I2"].BaseCall);
      Assert.AreEqual(BaseCall.Null, members["I3"].BaseCall);
      Assert.AreEqual(BaseCall.Null, members["I4"].BaseCall);

      members = GetHelper<HasBodyTestCase3>();
      Assert.AreEqual(BaseCall.Default, members["I1", false].BaseCall, "class, I1, not explicit");
      Assert.AreEqual(BaseCall.Explicit, members["I2", true].BaseCall, "class, I2, explicit");
      Assert.AreEqual(BaseCall.Default, members["I2", false].BaseCall, "class, I2, not explicit");
      Assert.AreEqual(BaseCall.Explicit, members["I3", true].BaseCall, "class, I3, explicit");
      Assert.AreEqual(BaseCall.Default, members["I3", false].BaseCall, "class, I3, not explicit");
      Assert.AreEqual(BaseCall.Explicit, members["I4", true].BaseCall, "class, I4, explicit");
      Assert.AreEqual(BaseCall.Null, members["I4", false].BaseCall, "class, I4, not explicit");
    }
  }
}
